#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/signal.h>
#include <sys/wait.h>
#include <errno.h>
#include <time.h>
#include "error.h"
//#include "cliser.h"
#include "clsv.h"
#include "sockcomADT.h"
#include "server.h"
#include "svstub.h"
#include "./cypher/cypher.h"
#include "./dbHandler/dbHandler.h"
#include "hton.h"
#include "ntoh.h"
#include "tickets.h"
#include "cypher/cypher.h"
#include "conf.h"

#define	HOST			"localhost"
#define	PORT			"4500"

#define	FILE_BUFFER		MAX_DATA

static pid_t servpid;
static int timeout= 0;

static int check_login(DATA * p, OPC opcode);
static int process_get_ticket(DATA * p, int offset, int login);
static int process_use_ticket(DATA * p, int offset, int login);
static int process_passwd(DATA * p, int offset, int login);
static int process_list_categories(DATA * p, int offset, int login);
static int process_list_users(DATA * p, int offset, int login);
static int process_list_files(DATA * p, int offset, int login);
static int process_upload(DATA * p, int offset, int login);
 
/* trasport stream buffer */
static CLSV_BUFFER clsv_buff;

static int (*proc[])(DATA * p, int offset, int login) =
{
	process_passwd,
	process_get_ticket,
	process_use_ticket,
	process_upload,
	process_list_categories,
	process_list_users,
	process_list_files
};

 
static int
check_login(DATA * p, OPC opcode)
{
	char user_pass[MAX_EPASS], user[MAX_USER+1], pass[MAX_EPASS];
	
	/* extract user and password */
	memcpy((void *)user, (void *)p, MAX_USER+1);
	memcpy((void *)pass, (void *)((char *)p+MAX_USER+1), MAX_EPASS);
	
	if (GetPass(_DB_PATH_, user_pass, user) == -1)
		return ERROR_ALLOGGEDIN;
			
	if (decypher2(pass, MAX_EPASS, user_pass) == -1)
		return ERROR_ALLOGGEDIN;
	
	else
		return do_checklogin("proto.db", pass, user);
}

static int
process_get_ticket(DATA * p, int offset, int login)
{
	ticketP tickp;
	int ret;
	char curr_pass[MAX_PASS+1];
	
	if (login <= 0)
	{
		p->svcl_get_ticket.status = login;
		return 0;
	}
	
	/* get password */
	if ((ret = GetPass("proto.db", curr_pass, p->clsv_get_ticket.user) < 0))
	{
		p->svcl_get_ticket.status = ret;
		return 0;
	}
	
	if ((ret = decypher2(p->clsv_get_ticket.filename, MAX_FILENAME,
						 curr_pass)) < 0)
	{
		p->svcl_get_ticket.status = ret;
		return 0;
	}
	
	if ((ret = get_ticket(p->clsv_get_ticket.filename, &tickp, login)) < 0)
	{
		p->svcl_get_ticket.status = ret;
		return 0;
	}
	
	/* if everything is OK, copy ticket info to send to client */
	p->svcl_get_ticket.status = tickp.retries;
	p->svcl_get_ticket.dtime = tickp.dtime;
	p->svcl_get_ticket.etime = tickp.etime;
	p->svcl_get_ticket.ftime = tickp.ftime;
	
	//printTicketParameters(&tickp);
	
	strcpy(p->svcl_get_ticket.id, tickp.id);
	if ((ret = cypher2(p->svcl_get_ticket.id, MAX_TICKET, curr_pass)) < 0)
		p->svcl_get_ticket.status = ret;
	
	return 0;
}

static int
process_use_ticket(DATA * p, int offset, int login)
{
	ticketP tickp;
	int ret;
	char curr_pass[MAX_PASS+1];
	char filename[MAX_FILENAME+1];
	int qty, bytes;
	FILE * file;
	char * buffer[FILE_BUFFER];
	void * stream;
	
	if (login <= 0)
	{
		p->comm_data_block.status = login;
		return 0;
	}
	
	/* get password */
	if ((ret = GetPass("proto.db", curr_pass, p->clsv_use_ticket.user) < 0))
	{
		p->comm_data_block.status = ret;
		return 0;
	}
	
	/* decypher requested ticket id */
	if ((ret = decypher2(p->clsv_use_ticket.ticket_id, MAX_ETICKET,
						 curr_pass)) < 0)
	{
		p->comm_data_block.status = ret;
		return 0;
	}

	/* I ensure it ends with '\0' */
	p->clsv_use_ticket.ticket_id[MAX_TICKET] = '\0';
	
		
	if ((ret = use_ticket(p->clsv_use_ticket.ticket_id, &tickp, login)) < 0)
	{
		p->comm_data_block.status = ret;
		return 0;
	}
	
	getFilenameByID("proto.db", p->clsv_use_ticket.ticket_id, filename);
	//printf("Empieza a mandar el archivo %s\n", filename);
	
	if ((stream = malloc(sizeof(CLSV_BUFFER))) == NULL)
		fprintf(stderr, "Error allocating memory\n");
	
	if ((file = fopen(filename, "r")) == NULL)
	{
		p->comm_data_block.status = -ERROR_FILE_NOT_EXISTS;
		return 0;
	}
		
	while (qty = fread(buffer, 1, FILE_BUFFER, file))
	{
		p->comm_data_block.status = qty;
		memcpy(p->comm_data_block.data, buffer, qty);
		/* convert struct to stream and send reply */
		bytes = hton_clsv_buffer(&clsv_buff, &stream, USE_TICKET, COMM);
		send_packet(comm, stream, bytes, _TCP_);
	}
	
	free(stream);
	p->comm_data_block.status = ret;
	return 0;
}

static int
process_upload(DATA * p, int offset, int login)
{
	printf("process_upload\n");
}

static int
process_passwd(DATA * p, int offset, int login)
{
	STAT status;
	char current_pass[MAX_PASS+1];
	
	if (login <= 0)
	{
		p->svcl_passwd.status = login;
		return 0;
	}
	
	else
	{
		if ((status = GetPass(_DB_PATH_, current_pass,
							  p->clsv_passwd.user)) < 0)
			p->svcl_passwd.status = status;

		if ((status = decypher2(p->clsv_passwd.oldpass, MAX_EPASS,
								current_pass)) < 0)
			p->svcl_passwd.status = status;
			
		if ((status = decypher2(p->clsv_passwd.newpass, MAX_EPASS,
								current_pass)) < 0)
			p->svcl_passwd.status = status;

		p->svcl_passwd.status =
		passwd(p->clsv_passwd.oldpass, p->clsv_passwd.newpass, login);
	}
	return 0;
}

static int
process_list_categories(DATA * p, int offset, int login)
{
	p->comm_list_block.status = list_categories(offset, p->comm_list_block.data);
	return p->comm_list_block.status+1;
}

static int
process_list_users(DATA * p, int offset, int login)
{	
	if (login > 0)
	{
		p->comm_list_block.status = list_users(offset, p->comm_list_block.data);
		return p->comm_list_block.status+1;
	}
	else
	{
		printf("Login failed\n");
		p->comm_list_block.status = login;
		return 0;
	}
}

static int
process_list_files(DATA * p, int offset, int login)
{	
	if (login > 0)
	{
		p->comm_list_block.status = list_files(offset,
						p->clsv_list_files.category, p->comm_list_block.data);
		return p->comm_list_block.status+1;
	}
	else
	{
		p->comm_list_block.status = login;
		return 0;
	}
}


int
start_session(commType com, char * host, char * port)
{
    if ((comm = initComm(com, host, port)) == NULL)
	{
		fprintf(stderr, "Sockets creation failed\n");
		exit(1);
	}
    return 1;
}

void
do_server(Comm comm)
{
	char * aux;
	int sock;

	if ((sock= acceptComm(comm)) >= 0)
		printf("Connection from %s established.\n", printIP(comm));
	else
	{
		if (errno == EINTR)
			return;
		errexit("accept: %s\n", strerror(errno));
	}
	signal(SIGCHLD, reaper);

	switch(fork())
	{
		case 0:					/* child */
			closeComm(comm, SERVER);
			process_server(comm);
			exit(1);
		default:				/* parent */
			closeComm(comm, CLIENT);
			break;
		case -1:
			errexit("fork: %s\n", strerror(errno));
	}
}

void
process_server(Comm comm)
{
	int qty, bytes, login= 0, encript= 0, next= 1, offset= 0;
	OPC opcode;
	CLSV_BUFFER * p = &clsv_buff;
	cypherT cdata;
	//ECLSV_BUFFER ep;
	char * pass, file[MAX_FILENAME+1];
	void * stream;
	
	if ((stream = malloc(sizeof(CLSV_BUFFER))) == NULL)
		fprintf(stderr, "Error allocating memory\n");

	receive_packet(comm, stream, sizeof(CLSV_BUFFER), _TCP_);
	
	/* convert received stream to corresponding struct */
	ntoh_clsv_buffer(&p, stream, CL);
	opcode = p->opcode;
	printf("received packet opcode %d\n", opcode);
	
	/* process requested operation */
	if (opcode >= 0 && opcode < MAX_OPCODES)
	{
		/* check login for authenticated functions */
		if (opcode == PASSWD || opcode == GET_TICKET || opcode == USE_TICKET
			|| opcode == LIST_USERS || opcode == LIST_FILES)
			login = check_login(&(p->data), opcode);
		
		while (next)
		{
			next = (*proc[opcode])(&(p->data), next-1, login);
			
			if (opcode != USE_TICKET)
			{
				/* convert struct to stream and send reply */
				bytes = hton_clsv_buffer(p, &stream, opcode, SV);
				send_packet(comm, stream, bytes, _TCP_);
			}
		}
	}
	else
		/* unrecognized opcode. error handler needed */
		fprintf(stderr, "Error: Incorrect opcode\n");

	free(stream);
}

void
takeUDPResquest(Comm comm)
{
	CLSV_BUFFER * p = &clsv_buff;
	void * stream;
	int bytes;

	if ((stream = malloc(sizeof(CLSV_BUFFER))) == NULL)
		fprintf(stderr, "Error allocating memory\n");

	receive_UDP(comm, stream, sizeof(CLSV_BUFFER));
	ntoh_clsv_buffer(&p, stream, CL);

	//(*proc[p->opcode])(&(p->data), 0, &login);
	process_UDP(p);

	bytes= hton_clsv_buffer(p, &stream, p->opcode, SV);
	send_UDP(comm, stream, bytes);

	free(stream);
	return;
}

void
process_UDP(CLSV_BUFFER * p)
{
	int login= 0;

	switch( p->opcode )
	{
		case PASSWD:
			login= checkID(_DB_PATH_, p->data.clsv_passwd.user);
			break;
		//case RLOGIN:
			//login= checkID(_DB_PATH_, p->data.clsv_rlogin.user);
	}
	(*proc[p->opcode])(&(p->data), 0, login);	
	return;
}

void 
siginthandler(int sig)
{
	if( servpid == getpid() )
	{
		printf("Stoping server...\t\t\t");
		closeComm(comm, SERVER);
		printf("[\x1b[%d;%dmOK\x1b[0m]\n", 0, 32);
	}
	else
		closeComm(comm, CLIENT);
	exit(1);
}

void
sigtstphandler(int sig)
{
	
	if( servpid == getpid() )
	{
		system("clear");
		printf("Active Connections:\n\n\nIP\t\tUser\tFile\tStatus\n");
		printf("-------------------------------------------------------\n\n");
		printCons(_DB_PATH_);
		printf("\n\n\nServer Running! (Press 'Ctrl+C' to stop it,"
			" 'Ctrl+Z' to view active connections)\n");
	}
	return;	
}

void 
sigalrmhandler(int sig)
{
	printf("ALAAAAAAARM!\n");
	timeout= 1;
	return;
}

void
reaper(int sig)
{
	int status;

//	printf("Connection from %s closed.\n", printIP(comm));
	while (wait3(&status, WNOHANG, (struct rusage *)0) > 0);
}

int
main(int argc, char * argv[])
{
	int port[MAX_PORTS];		/* ports in which the server will listen */
	char ip[MAX_IP_LEN];		/* IP in which the server will */
	int connections;			/* number of maximum simmultaneous connections */
	char dbname[MAX_FILEL];		/* database path */
	int i, n;
	char sport[10];
	
	servpid = getpid();
	
	system("clear");
	
	/* get configuration */
	printf("Loading Configuration...\t\t");
	if (argc == 1)
		n = getConf(CONF_FILE, port, ip, &connections, dbname);
	else
	{
		n = 1;
		parseConf(argc, argv, port, ip, &connections, dbname);
	}
	printf("[\x1b[%d;%dmOK\x1b[0m]\n", 0, 32);
	
	/* check database integrity */
	printf("Checking Database Integrity...\t\t");
	if (checkDB("proto.db"))
	{
		printf("\x1b[%d;%dmDB Error!\x1b[0m\n", 0, 31);
		return 1;
	}
	printf("[\x1b[%d;%dmOK\x1b[0m]\n", 0, 32);
	
	/* initialize connnection(s) */
	for (i=0; i<n; i++)
	{
		sprintf(sport, "%d", port[i]);
		start_session(COMM_SERVER, ip, sport);
	}
	printf("Starting Server...\t\t\t");
	if( comm == NULL )
	{
		printf("\x1b[%d;%dmFAIL!\x1b[0m\n", 0, 31);
		return 1;
	}
	printf("[\x1b[%d;%dmOK\x1b[0m]\n\nServer Running!"
			" (Press 'Ctrl+C' to stop it,"
			" 'Ctrl+Z' to view active connections)\n", 0, 32);
	
	/* interruption handlers */
	signal(SIGINT, siginthandler);
	signal(SIGTSTP, sigtstphandler);
	signal(SIGALRM, sigalrmhandler);
	
	forever
	{
		switch (sockSel(comm))
		{
			//case 0:
			//	errexit("select error: %s\n", strerror(errno)); break;
			case _TCP_:
				do_server(comm); break;
			case _UDP_:
				takeUDPResquest(comm);
		}
	}
	
	return 0;
}
